package com.ruihu.admin.handler;

import cn.hutool.core.util.StrUtil;
import com.ruihu.admin.common.anno.OperateLog;
import com.ruihu.admin.common.async.AsyncFactory;
import com.ruihu.admin.common.async.AsyncManager;
import com.ruihu.admin.common.constant.JudgeEnum;
import com.ruihu.admin.constant.CommonConst;
import com.ruihu.admin.module.business.log.useroperatelog.domain.UserOperateLogEntity;
import com.ruihu.admin.module.system.login.domain.RequestTokenBO;
import com.ruihu.admin.util.IPUtil;
import com.ruihu.admin.util.RequestTokenUtil;
import com.ruihu.admin.util.ServletUtil;
import com.ruihu.admin.util.StringUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.MDC;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;
/**
 * [  操作日志记录处理,对所有OperateLog注解的Controller进行操作日志监控 ]
 *
 * @author yandanyang
 * @version 1.0
 * @company Ruihu (Nanjing) Information Technology Co., Ltd
 * @copyright (c) 2019 Ruihu (Nanjing) Information Technology Co., LtdInc. All rights reserved.
 * @date
 * @since JDK1.8
 */
@Slf4j
@Aspect
@Component
@Order(1)
public class OperateLogAspect extends AbstractLogAspect {

    /**
     * 请求参数
     */
    private ThreadLocal<String> requestParamsThreadLocal = new ThreadLocal<>();

    @Pointcut("execution(* com.ruihu.admin.module..*Controller.*(..)))")
    public void operateLogPoint() {
    }

    @Before("operateLogPoint()")
    public void doBefore(JoinPoint joinPoint) {
        HttpServletRequest request = ServletUtil.getRequest();

        // 获取请求参数
        String requestParam = this.getRequestValue(joinPoint, request.getMethod());
        this.requestParamsThreadLocal.set(requestParam);
        Method method = this.getMethod(joinPoint);
        // 生成TraceId放入MDC中
        MDC.put(CommonConst.Log.TRACE_ID, method.getName() + "_" + System.currentTimeMillis());

        // 打印请求相关参数
        log.info("========================================== Start ==========================================");
        // 打印请求 url
        log.info("URL            : {}", request.getRequestURL().toString());
        // 打印描述信息
        log.info("Description    : {}", this.getAspectLogDescription(joinPoint));
        // 打印 Http method
        log.info("HTTP Method    : {}", request.getMethod());
        // 打印调用 controller 的全路径以及执行方法
        log.info("Class Method   : {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
        // 打印请求的 IP
        log.info("IP             : {}", IPUtil.getRemoteIp(request));
        // 打印请求入参
        log.info("Request Args   : {}", requestParam);
        // 打印User Agent
        log.info("User Agent     : {}", request.getHeader("user-agent"));
    }

    @Around("operateLogPoint()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();

        Object result = null;
        try {
            result = proceedingJoinPoint.proceed();
        } catch (Exception ex) {
            log.error(ex.getMessage(), ex);
            throw ex;
        } finally {
            log.info("Response Args: {}", super.serializeResult(result));
            log.info("Time-Consuming: {} ms", System.currentTimeMillis() - startTime);
        }

        return result;
    }

    @After("operateLogPoint()")
    public void doAfter() throws Throwable {
        log.info("========================================== End ==========================================");
    }


    @AfterReturning(pointcut = "operateLogPoint()")
    public void doAfterReturning(JoinPoint joinPoint) {
        handleLog(joinPoint, null);
    }

    @AfterThrowing(value = "operateLogPoint()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
        handleLog(joinPoint, e);
    }

    protected void handleLog(final JoinPoint joinPoint, final Exception e) {
        try {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            OperateLog operateLog = this.getAnnotationLog(joinPoint);
            if (operateLog == null) {
                return;
            }
            RequestTokenBO requestToken = RequestTokenUtil.getRequestUser();
            if (requestToken == null) {
                return;
            }

            if (operateLog.saveRequestData()) {
                // 设置方法名称
                String className = joinPoint.getTarget().getClass().getName();
                String methodName = joinPoint.getSignature().getName();
                String operateMethod = className + "." + methodName;
                String params = this.requestParamsThreadLocal.get();
                String failReason = null;
                Integer result = JudgeEnum.YES.getValue();
                if (e != null) {
                    result = JudgeEnum.NO.getValue();
                    StringWriter sw = new StringWriter();
                    PrintWriter pw = new PrintWriter(sw, true);
                    e.printStackTrace(pw);
                    failReason = sw.toString();
                    pw.flush();
                    pw.close();
                    sw.flush();
                    sw.close();
                }
                UserOperateLogEntity operateLogEntity =
                        UserOperateLogEntity.builder().userId(requestToken.getRequestUserId()).username(requestToken.getUserBO().getActualName())
                                .url(request.getRequestURI()).method(operateMethod).param(params).failReason(failReason).result(result).build();
                ApiOperation apiOperation = this.getApiOperation(joinPoint);
                if (apiOperation != null) {
                    operateLogEntity.setContent(apiOperation.value());
                }
                Api api = this.getApi(joinPoint);
                if (api != null) {
                    String[] tags = api.tags();
                    operateLogEntity.setModule(StringUtil.join(tags, ","));
                }
                // 异步保存日志
                AsyncManager.me().executeIOTask(AsyncFactory.recordLogTask(operateLogEntity));
            }
        } catch (Exception exp) {
            log.error("保存操作日志异常:{}", exp.getMessage());
            exp.printStackTrace();
        }
    }

    private OperateLog getAnnotationLog(JoinPoint joinPoint) throws Exception {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        OperateLog classAnnotation = AnnotationUtils.findAnnotation(method.getDeclaringClass(), OperateLog.class);

        if (method != null) {
            return classAnnotation;
        }
        return null;
    }

    /**
     * swagger API
     *
     * @param joinPoint
     * @return
     * @throws Exception
     */
    private Api getApi(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        Api classAnnotation = AnnotationUtils.findAnnotation(method.getDeclaringClass(), Api.class);

        if (method != null) {
            return classAnnotation;
        }
        return null;
    }

    /**
     * swagger ApiOperation
     *
     * @param joinPoint
     * @return
     * @throws Exception
     */
    private ApiOperation getApiOperation(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();

        if (method != null) {
            return method.getAnnotation(ApiOperation.class);
        }
        return null;
    }

    /**
     * 获取切面注解的描述
     *
     * @param joinPoint 切点
     * @return 描述信息
     */
    public String getAspectLogDescription(JoinPoint joinPoint) {
        OperateLog operateLog = null;
        try {
            operateLog = this.getAnnotationLog(joinPoint);
            if (operateLog == null)
                return StrUtil.EMPTY;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }

        return operateLog.description();
    }

}
